<!DOCTYPE html><html><head>

    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="HandheldFriendly" content="true">

<meta charset="utf-8"><style>body {
  width: 45em;
  border: 1px solid #ddd;
  outline: 1300px solid #fff;
  margin: 16px auto;
}

body .markdown-body
{
  padding: 30px;
}

@font-face {
  font-family: fontawesome-mini;
  src: url(data:font/woff;charset=utf-8;base64,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) format('woff');
}

@font-face {
  font-family: octicons-anchor;
  src: url(data:font/woff;charset=utf-8;base64,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) format('woff');
}

.markdown-body {
  font-family: sans-serif;
  -ms-text-size-adjust: 100%;
  -webkit-text-size-adjust: 100%;
  color: #333333;
  overflow: hidden;
  font-family: "Helvetica Neue", Helvetica, "Segoe UI", Arial, freesans, sans-serif;
  font-size: 16px;
  line-height: 1.6;
  word-wrap: break-word;
}

.markdown-body a {
  background: transparent;
}

.markdown-body a:active,
.markdown-body a:hover {
  outline: 0;
}

.markdown-body b,
.markdown-body strong {
  font-weight: bold;
}

.markdown-body mark {
  background: #ff0;
  color: #000;
  font-style: italic;
  font-weight: bold;
}

.markdown-body sub,
.markdown-body sup {
  font-size: 75%;
  line-height: 0;
  position: relative;
  vertical-align: baseline;
}
.markdown-body sup {
  top: -0.5em;
}
.markdown-body sub {
  bottom: -0.25em;
}

.markdown-body h1 {
  font-size: 2em;
  margin: 0.67em 0;
}

.markdown-body img {
  border: 0;
}

.markdown-body hr {
  -moz-box-sizing: content-box;
  box-sizing: content-box;
  height: 0;
}

.markdown-body pre {
  overflow: auto;
}

.markdown-body code,
.markdown-body kbd,
.markdown-body pre,
.markdown-body samp {
  font-family: monospace, monospace;
  font-size: 1em;
}

.markdown-body input {
  color: inherit;
  font: inherit;
  margin: 0;
}

.markdown-body html input[disabled] {
  cursor: default;
}

.markdown-body input {
  line-height: normal;
}

.markdown-body input[type="checkbox"] {
  box-sizing: border-box;
  padding: 0;
}

.markdown-body table {
  border-collapse: collapse;
  border-spacing: 0;
}

.markdown-body td,
.markdown-body th {
  padding: 0;
}

.markdown-body .codehilitetable {
  border: 0;
  border-spacing: 0;
}

.markdown-body .codehilitetable tr {
  border: 0;
}

.markdown-body .codehilitetable pre,
.markdown-body .codehilitetable div.codehilite {
  margin: 0;
}

.markdown-body .linenos,
.markdown-body .code,
.markdown-body .codehilitetable td {
  border: 0;
  padding: 0;
}

.markdown-body td:not(.linenos) .linenodiv {
  padding: 0 !important;
}

.markdown-body .code {
  width: 100%;
}

.markdown-body .linenos div pre,
.markdown-body .linenodiv pre,
.markdown-body .linenodiv {
  border: 0;
  -webkit-border-radius: 0;
  -moz-border-radius: 0;
  border-radius: 0;
  -webkit-border-top-left-radius: 3px;
  -webkit-border-bottom-left-radius: 3px;
  -moz-border-radius-topleft: 3px;
  -moz-border-radius-bottomleft: 3px;
  border-top-left-radius: 3px;
  border-bottom-left-radius: 3px;
}

.markdown-body .code div pre,
.markdown-body .code div {
  border: 0;
  -webkit-border-radius: 0;
  -moz-border-radius: 0;
  border-radius: 0;
  -webkit-border-top-right-radius: 3px;
  -webkit-border-bottom-right-radius: 3px;
  -moz-border-radius-topright: 3px;
  -moz-border-radius-bottomright: 3px;
  border-top-right-radius: 3px;
  border-bottom-right-radius: 3px;
}

.markdown-body * {
  -moz-box-sizing: border-box;
  box-sizing: border-box;
}

.markdown-body input {
  font: 13px Helvetica, arial, freesans, clean, sans-serif, "Segoe UI Emoji", "Segoe UI Symbol";
  line-height: 1.4;
}

.markdown-body a {
  color: #4183c4;
  text-decoration: none;
}

.markdown-body a:hover,
.markdown-body a:focus,
.markdown-body a:active {
  text-decoration: underline;
}

.markdown-body hr {
  height: 0;
  margin: 15px 0;
  overflow: hidden;
  background: transparent;
  border: 0;
  border-bottom: 1px solid #ddd;
}

.markdown-body hr:before,
.markdown-body hr:after {
  display: table;
  content: " ";
}

.markdown-body hr:after {
  clear: both;
}

.markdown-body h1,
.markdown-body h2,
.markdown-body h3,
.markdown-body h4,
.markdown-body h5,
.markdown-body h6 {
  margin-top: 15px;
  margin-bottom: 15px;
  line-height: 1.1;
}

.markdown-body h1 {
  font-size: 30px;
}

.markdown-body h2 {
  font-size: 21px;
}

.markdown-body h3 {
  font-size: 16px;
}

.markdown-body h4 {
  font-size: 14px;
}

.markdown-body h5 {
  font-size: 12px;
}

.markdown-body h6 {
  font-size: 11px;
}

.markdown-body blockquote {
  margin: 0;
}

.markdown-body ul,
.markdown-body ol {
  padding: 0;
  margin-top: 0;
  margin-bottom: 0;
}

.markdown-body ol ol,
.markdown-body ul ol {
  list-style-type: lower-roman;
}

.markdown-body ul ul ol,
.markdown-body ul ol ol,
.markdown-body ol ul ol,
.markdown-body ol ol ol {
  list-style-type: lower-alpha;
}

.markdown-body dd {
  margin-left: 0;
}

.markdown-body code,
.markdown-body pre,
.markdown-body samp {
  font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
  font-size: 12px;
}

.markdown-body pre {
  margin-top: 0;
  margin-bottom: 0;
}

.markdown-body kbd {
  background-color: #e7e7e7;
  background-image: -moz-linear-gradient(#fefefe, #e7e7e7);
  background-image: -webkit-linear-gradient(#fefefe, #e7e7e7);
  background-image: linear-gradient(#fefefe, #e7e7e7);
  background-repeat: repeat-x;
  border-radius: 2px;
  border: 1px solid #cfcfcf;
  color: #000;
  padding: 3px 5px;
  line-height: 10px;
  font: 11px Consolas, "Liberation Mono", Menlo, Courier, monospace;
  display: inline-block;
}

.markdown-body>*:first-child {
  margin-top: 0 !important;
}

.markdown-body>*:last-child {
  margin-bottom: 0 !important;
}

.markdown-body .headeranchor-link {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  display: block;
  padding-right: 6px;
  padding-left: 30px;
  margin-left: -30px;
}

.markdown-body .headeranchor-link:focus {
  outline: none;
}

.markdown-body h1,
.markdown-body h2,
.markdown-body h3,
.markdown-body h4,
.markdown-body h5,
.markdown-body h6 {
  position: relative;
  margin-top: 1em;
  margin-bottom: 16px;
  font-weight: bold;
  line-height: 1.4;
}

.markdown-body h1 .headeranchor,
.markdown-body h2 .headeranchor,
.markdown-body h3 .headeranchor,
.markdown-body h4 .headeranchor,
.markdown-body h5 .headeranchor,
.markdown-body h6 .headeranchor {
  display: none;
  color: #000;
  vertical-align: middle;
}

.markdown-body h1:hover .headeranchor-link,
.markdown-body h2:hover .headeranchor-link,
.markdown-body h3:hover .headeranchor-link,
.markdown-body h4:hover .headeranchor-link,
.markdown-body h5:hover .headeranchor-link,
.markdown-body h6:hover .headeranchor-link {
  height: 1em;
  padding-left: 8px;
  margin-left: -30px;
  line-height: 1;
  text-decoration: none;
}

.markdown-body h1:hover .headeranchor-link .headeranchor,
.markdown-body h2:hover .headeranchor-link .headeranchor,
.markdown-body h3:hover .headeranchor-link .headeranchor,
.markdown-body h4:hover .headeranchor-link .headeranchor,
.markdown-body h5:hover .headeranchor-link .headeranchor,
.markdown-body h6:hover .headeranchor-link .headeranchor {
  display: inline-block;
}

.markdown-body h1 {
  padding-bottom: 0.3em;
  font-size: 2.25em;
  line-height: 1.2;
  border-bottom: 1px solid #eee;
}

.markdown-body h2 {
  padding-bottom: 0.3em;
  font-size: 1.75em;
  line-height: 1.225;
  border-bottom: 1px solid #eee;
}

.markdown-body h3 {
  font-size: 1.5em;
  line-height: 1.43;
}

.markdown-body h4 {
  font-size: 1.25em;
}

.markdown-body h5 {
  font-size: 1em;
}

.markdown-body h6 {
  font-size: 1em;
  color: #777;
}

.markdown-body p,
.markdown-body blockquote,
.markdown-body ul,
.markdown-body ol,
.markdown-body dl,
.markdown-body table,
.markdown-body pre,
.markdown-body .admonition {
  margin-top: 0;
  margin-bottom: 16px;
}

.markdown-body hr {
  height: 4px;
  padding: 0;
  margin: 16px 0;
  background-color: #e7e7e7;
  border: 0 none;
}

.markdown-body ul,
.markdown-body ol {
  padding-left: 2em;
}

.markdown-body ul ul,
.markdown-body ul ol,
.markdown-body ol ol,
.markdown-body ol ul {
  margin-top: 0;
  margin-bottom: 0;
}

.markdown-body li>p {
  margin-top: 16px;
}

.markdown-body dl {
  padding: 0;
}

.markdown-body dl dt {
  padding: 0;
  margin-top: 16px;
  font-size: 1em;
  font-style: italic;
  font-weight: bold;
}

.markdown-body dl dd {
  padding: 0 16px;
  margin-bottom: 16px;
}

.markdown-body blockquote {
  padding: 0 15px;
  color: #777;
  border-left: 4px solid #ddd;
}

.markdown-body blockquote>:first-child {
  margin-top: 0;
}

.markdown-body blockquote>:last-child {
  margin-bottom: 0;
}

.markdown-body table {
  display: block;
  width: 100%;
  overflow: auto;
  word-break: normal;
  word-break: keep-all;
}

.markdown-body table th {
  font-weight: bold;
}

.markdown-body table th,
.markdown-body table td {
  padding: 6px 13px;
  border: 1px solid #ddd;
}

.markdown-body table tr {
  background-color: #fff;
  border-top: 1px solid #ccc;
}

.markdown-body table tr:nth-child(2n) {
  background-color: #f8f8f8;
}

.markdown-body img {
  max-width: 100%;
  -moz-box-sizing: border-box;
  box-sizing: border-box;
}

.markdown-body code,
.markdown-body samp {
  padding: 0;
  padding-top: 0.2em;
  padding-bottom: 0.2em;
  margin: 0;
  font-size: 85%;
  background-color: rgba(0,0,0,0.04);
  border-radius: 3px;
}

.markdown-body code:before,
.markdown-body code:after {
  letter-spacing: -0.2em;
  content: "\00a0";
}

.markdown-body pre>code {
  padding: 0;
  margin: 0;
  font-size: 100%;
  word-break: normal;
  white-space: pre;
  background: transparent;
  border: 0;
}

.markdown-body .codehilite {
  margin-bottom: 16px;
}

.markdown-body .codehilite pre,
.markdown-body pre {
  padding: 16px;
  overflow: auto;
  font-size: 85%;
  line-height: 1.45;
  background-color: #f7f7f7;
  border-radius: 3px;
}

.markdown-body .codehilite pre {
  margin-bottom: 0;
  word-break: normal;
}

.markdown-body pre {
  word-wrap: normal;
}

.markdown-body pre code {
  display: inline;
  max-width: initial;
  padding: 0;
  margin: 0;
  overflow: initial;
  line-height: inherit;
  word-wrap: normal;
  background-color: transparent;
  border: 0;
}

.markdown-body pre code:before,
.markdown-body pre code:after {
  content: normal;
}

/* Admonition */
.markdown-body .admonition {
  -webkit-border-radius: 3px;
  -moz-border-radius: 3px;
  position: relative;
  border-radius: 3px;
  border: 1px solid #e0e0e0;
  border-left: 6px solid #333;
  padding: 10px 10px 10px 30px;
}

.markdown-body .admonition table {
  color: #333;
}

.markdown-body .admonition p {
  padding: 0;
}

.markdown-body .admonition-title {
  font-weight: bold;
  margin: 0;
}

.markdown-body .admonition>.admonition-title {
  color: #333;
}

.markdown-body .attention>.admonition-title {
  color: #a6d796;
}

.markdown-body .caution>.admonition-title {
  color: #d7a796;
}

.markdown-body .hint>.admonition-title {
  color: #96c6d7;
}

.markdown-body .danger>.admonition-title {
  color: #c25f77;
}

.markdown-body .question>.admonition-title {
  color: #96a6d7;
}

.markdown-body .note>.admonition-title {
  color: #d7c896;
}

.markdown-body .admonition:before,
.markdown-body .attention:before,
.markdown-body .caution:before,
.markdown-body .hint:before,
.markdown-body .danger:before,
.markdown-body .question:before,
.markdown-body .note:before {
  font: normal normal 16px fontawesome-mini;
  -moz-osx-font-smoothing: grayscale;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
  line-height: 1.5;
  color: #333;
  position: absolute;
  left: 0;
  top: 0;
  padding-top: 10px;
  padding-left: 10px;
}

.markdown-body .admonition:before {
  content: "\f056\00a0";
  color: 333;
}

.markdown-body .attention:before {
  content: "\f058\00a0";
  color: #a6d796;
}

.markdown-body .caution:before {
  content: "\f06a\00a0";
  color: #d7a796;
}

.markdown-body .hint:before {
  content: "\f05a\00a0";
  color: #96c6d7;
}

.markdown-body .danger:before {
  content: "\f057\00a0";
  color: #c25f77;
}

.markdown-body .question:before {
  content: "\f059\00a0";
  color: #96a6d7;
}

.markdown-body .note:before {
  content: "\f040\00a0";
  color: #d7c896;
}

.markdown-body .admonition::after {
  content: normal;
}

.markdown-body .attention {
  border-left: 6px solid #a6d796;
}

.markdown-body .caution {
  border-left: 6px solid #d7a796;
}

.markdown-body .hint {
  border-left: 6px solid #96c6d7;
}

.markdown-body .danger {
  border-left: 6px solid #c25f77;
}

.markdown-body .question {
  border-left: 6px solid #96a6d7;
}

.markdown-body .note {
  border-left: 6px solid #d7c896;
}

.markdown-body .admonition>*:first-child {
  margin-top: 0 !important;
}

.markdown-body .admonition>*:last-child {
  margin-bottom: 0 !important;
}

/* progress bar*/
.markdown-body .progress {
  display: block;
  width: 300px;
  margin: 10px 0;
  height: 24px;
  -webkit-border-radius: 3px;
  -moz-border-radius: 3px;
  border-radius: 3px;
  background-color: #ededed;
  position: relative;
  box-shadow: inset -1px 1px 3px rgba(0, 0, 0, .1);
}

.markdown-body .progress-label {
  position: absolute;
  text-align: center;
  font-weight: bold;
  width: 100%; margin: 0;
  line-height: 24px;
  color: #333;
  text-shadow: 1px 1px 0 #fefefe, -1px -1px 0 #fefefe, -1px 1px 0 #fefefe, 1px -1px 0 #fefefe, 0 1px 0 #fefefe, 0 -1px 0 #fefefe, 1px 0 0 #fefefe, -1px 0 0 #fefefe, 1px 1px 2px #000;
  -webkit-font-smoothing: antialiased !important;
  white-space: nowrap;
  overflow: hidden;
}

.markdown-body .progress-bar {
  height: 24px;
  float: left;
  -webkit-border-radius: 3px;
  -moz-border-radius: 3px;
  border-radius: 3px;
  background-color: #96c6d7;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .5), inset 0 -1px 0 rgba(0, 0, 0, .1);
  background-size: 30px 30px;
  background-image: -webkit-linear-gradient(
    135deg, rgba(255, 255, 255, .4) 27%,
    transparent 27%,
    transparent 52%, rgba(255, 255, 255, .4) 52%,
    rgba(255, 255, 255, .4) 77%,
    transparent 77%, transparent
  );
  background-image: -moz-linear-gradient(
    135deg,
    rgba(255, 255, 255, .4) 27%, transparent 27%,
    transparent 52%, rgba(255, 255, 255, .4) 52%,
    rgba(255, 255, 255, .4) 77%, transparent 77%,
    transparent
  );
  background-image: -ms-linear-gradient(
    135deg,
    rgba(255, 255, 255, .4) 27%, transparent 27%,
    transparent 52%, rgba(255, 255, 255, .4) 52%,
    rgba(255, 255, 255, .4) 77%, transparent 77%,
    transparent
  );
  background-image: -o-linear-gradient(
    135deg,
    rgba(255, 255, 255, .4) 27%, transparent 27%,
    transparent 52%, rgba(255, 255, 255, .4) 52%,
    rgba(255, 255, 255, .4) 77%, transparent 77%,
    transparent
  );
  background-image: linear-gradient(
    135deg,
    rgba(255, 255, 255, .4) 27%, transparent 27%,
    transparent 52%, rgba(255, 255, 255, .4) 52%,
    rgba(255, 255, 255, .4) 77%, transparent 77%,
    transparent
  );
}

.markdown-body .progress-100plus .progress-bar {
  background-color: #a6d796;
}

.markdown-body .progress-80plus .progress-bar {
  background-color: #c6d796;
}

.markdown-body .progress-60plus .progress-bar {
  background-color: #d7c896;
}

.markdown-body .progress-40plus .progress-bar {
  background-color: #d7a796;
}

.markdown-body .progress-20plus .progress-bar {
  background-color: #d796a6;
}

.markdown-body .progress-0plus .progress-bar {
  background-color: #c25f77;
}

.markdown-body .candystripe-animate .progress-bar{
  -webkit-animation: animate-stripes 3s linear infinite;
  -moz-animation: animate-stripes 3s linear infinite;
  animation: animate-stripes 3s linear infinite;
}

@-webkit-keyframes animate-stripes {
  0% {
    background-position: 0 0;
  }

  100% {
    background-position: 60px 0;
  }
}

@-moz-keyframes animate-stripes {
  0% {
    background-position: 0 0;
  }

  100% {
    background-position: 60px 0;
  }
}

@keyframes animate-stripes {
  0% {
    background-position: 0 0;
  }

  100% {
    background-position: 60px 0;
  }
}

.markdown-body .gloss .progress-bar {
  box-shadow:
    inset 0 4px 12px rgba(255, 255, 255, .7),
    inset 0 -12px 0 rgba(0, 0, 0, .05);
}

/* Multimarkdown Critic Blocks */
.markdown-body .critic_mark {
  background: #ff0;
}

.markdown-body .critic_delete {
  color: #c82829;
  text-decoration: line-through;
}

.markdown-body .critic_insert {
  color: #718c00 ;
  text-decoration: underline;
}

.markdown-body .critic_comment {
  color: #8e908c;
  font-style: italic;
}

.markdown-body .headeranchor {
  font: normal normal 16px octicons-anchor;
  line-height: 1;
  display: inline-block;
  text-decoration: none;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}

.headeranchor:before {
  content: '\f05c';
}

.markdown-body .task-list-item {
  list-style-type: none;
}

.markdown-body .task-list-item+.task-list-item {
  margin-top: 3px;
}

.markdown-body .task-list-item input {
  margin: 0 4px 0.25em -20px;
  vertical-align: middle;
}

/* Media */
@media only screen and (min-width: 480px) {
  .markdown-body {
    font-size:14px;
  }
}

@media only screen and (min-width: 768px) {
  .markdown-body {
    font-size:16px;
  }
}

@media print {
  .markdown-body * {
    background: transparent !important;
    color: black !important;
    filter:none !important;
    -ms-filter: none !important;
  }

  .markdown-body {
    font-size:12pt;
    max-width:100%;
    outline:none;
    border: 0;
  }

  .markdown-body a,
  .markdown-body a:visited {
    text-decoration: underline;
  }

  .markdown-body .headeranchor-link {
    display: none;
  }

  .markdown-body a[href]:after {
    content: " (" attr(href) ")";
  }

  .markdown-body abbr[title]:after {
    content: " (" attr(title) ")";
  }

  .markdown-body .ir a:after,
  .markdown-body a[href^="javascript:"]:after,
  .markdown-body a[href^="#"]:after {
    content: "";
  }

  .markdown-body pre {
    white-space: pre;
    white-space: pre-wrap;
    word-wrap: break-word;
  }

  .markdown-body pre,
  .markdown-body blockquote {
    border: 1px solid #999;
    padding-right: 1em;
    page-break-inside: avoid;
  }

  .markdown-body .progress,
  .markdown-body .progress-bar {
    -moz-box-shadow: none;
    -webkit-box-shadow: none;
    box-shadow: none;
  }

  .markdown-body .progress {
    border: 1px solid #ddd;
  }

  .markdown-body .progress-bar {
    height: 22px;
    border-right: 1px solid #ddd;
  }

  .markdown-body tr,
  .markdown-body img {
    page-break-inside: avoid;
  }

  .markdown-body img {
    max-width: 100% !important;
  }

  .markdown-body p,
  .markdown-body h2,
  .markdown-body h3 {
    orphans: 3;
    widows: 3;
  }

  .markdown-body h2,
  .markdown-body h3 {
    page-break-after: avoid;
  }
}
</style><title>msp430lib-tutorial</title></head><body><article class="markdown-body"><h1 id="msp430"><a name="user-content-msp430" href="#msp430" class="headeranchor-link" aria-hidden="true"><span class="headeranchor"></span></a>MSP430 驱动库学习笔记</h1>
<div class="toc">
<ul>
<li><a href="#msp430">MSP430 驱动库学习笔记</a><ul>
<li><a href="#_1">开发环境</a></li>
<li><a href="#_2">库文件概览</a><ul>
<li><a href="#_3">新建工程</a></li>
</ul>
</li>
<li><a href="#_4">例程</a><ul>
<li><a href="#_5">基础篇</a><ul>
<li><a href="#_6">时钟</a></li>
<li><a href="#_7">看门狗</a></li>
<li><a href="#gpio">GPIO</a></li>
<li><a href="#_8">定时器</a></li>
<li><a href="#flash">内部Flash</a></li>
<li><a href="#adc">ADC</a></li>
<li><a href="#dac">DAC</a></li>
</ul>
</li>
<li><a href="#_9">通信篇</a><ul>
<li><a href="#uart">UART</a></li>
<li><a href="#iic">IIC</a></li>
<li><a href="#spi">SPI</a></li>
<li><a href="#usb">USB</a></li>
<li><a href="#sdio">SDIO</a></li>
</ul>
</li>
<li><a href="#_10">其他篇</a><ul>
<li><a href="#crc">CRC</a></li>
<li><a href="#aes">AES</a></li>
<li><a href="#mlp">MLP</a></li>
</ul>
</li>
</ul>
</li>
<li><a href="#_11">实践</a><ul>
<li><a href="#oled">显示屏(OLED)</a></li>
<li><a href="#lis3dhh3lis331dl">重力传感器(LIS3DH/H3LIS331DL)</a></li>
<li><a href="#flashmx66l51235f">外部Flash(MX66L51235F)</a></li>
<li><a href="#cc2540">蓝牙透传(CC2540)</a></li>
</ul>
</li>
<li><a href="#rtos">RTOS</a><ul>
<li><a href="#pt">PT</a></li>
<li><a href="#cos">μC/OS</a></li>
</ul>
</li>
</ul>
</li>
</ul>
</div>
<h2 id="_1"><a name="user-content-_1" href="#_1" class="headeranchor-link" aria-hidden="true"><span class="headeranchor"></span></a>开发环境</h2>
<ol>
<li>MSP430F5xxx系列开发板</li>
<li>MSP-FET430UIF仿真器</li>
<li>USB-串口转接板</li>
<li><a href="http://www.ti.com/tool/ccstudio-msp430">MSP430 CCS v5 IDE</a></li>
<li><a href="http://www.ti.com/tool/msp430driverlib">MSP430 Driver Library</a></li>
</ol>
<h2 id="_2"><a name="user-content-_2" href="#_2" class="headeranchor-link" aria-hidden="true"><span class="headeranchor"></span></a>库文件概览</h2>
<p>假设，你已经准备好了开发环境的前4条，然后从<a href="http://www.ti.com/tool/msp430driverlib">官方</a>下载到最新版本的驱动库，本文以<code>msp430_driverlib_1_90_00_65.zip</code>为例进行开发，先来看看官方是如何介绍<strong>MSP430驱动库</strong>的：</p>
<blockquote>
<p>What is MSP430 Driver Library?<br />
MSP430 Driver Library is a collection of high level APIs that speed up software development for MSP430. The MSP430 Driver Library package includes API documentaion and examples to help you get started. </p>
</blockquote>
<p>简而言之，就和STM32的固件库是一个类似的东西，是将底层的寄存器进行封装，我们只需要操作上层的API接口。这样子可以加快工程师的开发速度，快速的搭建我们需要的原型，并且相对于直接操作寄存器，其在可移植性和代码逻辑的可读性上也更胜一筹。</p>
<p>目前驱动库支持以下的MSP430器件类型，幸运的是，我们使用的<strong>MSP430F5510</strong>在此之列:</p>
<ul>
<li>MSP430F5xx_6xx</li>
<li>MSP430FR57xx</li>
<li>MSP430FR5xx_6xx</li>
<li>MSP430i2xx</li>
</ul>
<p>现在将下载好的<code>msp430_driverlib_1_90_00_65.zip</code>解压之后，进入<code>.\msp430_driverlib_1_90_00_65\driverlib_1_90_00_65</code>文件夹，其目录如下所示：</p>
<p><img alt="MSP430驱动库目录" src="http://mint-blog.qiniudn.com/msp430lib-MSP430驱动库目录.png" /></p>
<p>由于我们的芯片是<code>MSP430F5510</code>，所以关心的是带<code>MSP430F5xx_6xx</code>的内容，</p>
<ul>
<li>文档：<code>\doc\MSP430F5xx_6xx</code></li>
<li>驱动库：<code>\driverlib\MSP430F5xx_6xx</code></li>
<li>例程：<code>\examples\MSP430F5xx_6xx</code></li>
</ul>
<h3 id="_3"><a name="user-content-_3" href="#_3" class="headeranchor-link" aria-hidden="true"><span class="headeranchor"></span></a>新建工程</h3>
<p>文档相当于一个字典，当我们需要查什么是在去看。这里先简要的介绍了如何快速的建立以驱动库为基础的模板工程。<br />
1、<code>Project-&gt;New CCS Project</code>填写工程名，选择器件类型，按<code>Finish</code>完成。</p>
<p><img alt="NewCCSProject" src="http://mint-blog.qiniudn.com/msp430lib-NewCCSProject.png" /></p>
<p>2、配置项目并添加驱动库</p>
<p>新建完工程之后，其工程目录如下图所示：</p>
<p><img alt="工程目录" src="http://mint-blog.qiniudn.com/msp430lib-工程目录.png" /></p>
<ul>
<li><code>Includes</code>：这里就是我们刚才在选择器件类型时，CCS帮我们自动添加的头文件，主要是MSP430的寄存器头文件。</li>
<li><code>Lnk_msp430f5510.cmd</code>：这是链接之后生成的内存分布图，指定了变量在哪一块内存地址上，代码段在哪一段Flash地址上。</li>
<li><code>main.c</code>：基本为空的一个入口函数<code>main</code>所在的文件。</li>
<li><code>MSP430F5510.ccxml</code>：这是我们的配置文件，CCS识别MSP430项目基本就靠这个配置文件。</li>
</ul>
<p>在项目上点击右键，选择<code>Properties-&gt;Optimization</code>，将其优化等级关闭，其原因是在调试的时候，CCS过度的优化，将它认为无用的变量和语句忽略掉，是的，连语句也给忽略掉了，强(keng)大(die)的编译器。</p>
<p><img alt="关闭编译优化" src="http://mint-blog.qiniudn.com/msp430lib-关闭编译优化.png" /></p>
<p>然后，将我们需要的驱动库<code>\driverlib\MSP430F5xx_6xx</code>复制到我们实际文件夹的目录，并且打开<code>Include Options-&gt;Add..-&gt;Workspalce-&gt;Toggle the GPIO-&gt;MSP430F5xx_6xx</code>，完成头文件的添加。其作用就是在我们引用头文件的时候，不用添加绝对路径的地址，因为CCS会将其设置为系统当前路径，和C语言以及MSP430的头文件的待遇一样。</p>
<p><img alt="添加库文件路径" src="http://mint-blog.qiniudn.com/msp430lib-添加库文件路径.png" /></p>
<p>3、添加自己的代码</p>
<p>默认项目新建完成之后，main函数的代码非常简单。<br />
<pre><code class="c">/*
 * main.c
 */
void main(void) {

}
</code></pre></p>
<p>我们需要去看看TI给我们的例程：<code>\examples\MSP430F5xx_6xx</code>下的<code>gpio_ex1_outputHi.c</code>，去掉其BSD声明如下：<br />
<pre><code class="c">//******************************************************************************
//   Write a Word to Port A (Port1+Port2)
//
//   Writes a Word(FFFFh) to Port A and stays in LPM4
//   ACLK = 32.768kHz, MCLK = SMCLK = default DCO
//
//  Tested On: MSP430F5529, MSP430FR5739
//             -----------------
//         /|\|                 |
//          | |                 |
//          --|RST          PA.x|--&gt;HI
//            |                 |
//            |                 |
//
//
//   This example uses the following peripherals and I/O signals.  You must
//   review these and change as needed for your own board:
//   - GPIO Port peripheral
//
//   This example uses the following interrupt handlers.  To use this example
//   in your own application you must add these interrupt handlers to your
//   vector table.
//   - None.
//******************************************************************************
#include &quot;driverlib.h&quot;

void main(void)
{
        //Stop WDT
        WDT_A_hold(WDT_A_BASE);

        //PA.x output
        GPIO_setAsOutputPin(
                GPIO_PORT_PA,
                GPIO_PIN0 + GPIO_PIN1 + GPIO_PIN2 + GPIO_PIN3 +
                GPIO_PIN4 + GPIO_PIN5 + GPIO_PIN6 + GPIO_PIN7 +
                GPIO_PIN8 + GPIO_PIN9 + GPIO_PIN10 + GPIO_PIN11 +
                GPIO_PIN12 + GPIO_PIN13 + GPIO_PIN14 + GPIO_PIN15
                );


        //Set all PA pins HI
        GPIO_setOutputHighOnPin(

                GPIO_PORT_PA,
                GPIO_PIN0 + GPIO_PIN1 + GPIO_PIN2 + GPIO_PIN3 +
                GPIO_PIN4 + GPIO_PIN5 + GPIO_PIN6 + GPIO_PIN7 +
                GPIO_PIN8 + GPIO_PIN9 + GPIO_PIN10 + GPIO_PIN11 +
                GPIO_PIN12 + GPIO_PIN13 + GPIO_PIN14 + GPIO_PIN15
                );

        //Enter LPM4 w/interrupts enabled
        __bis_SR_register(LPM4_bits + GIE);

        //For debugger
        __no_operation();
}
</code></pre></p>
<p>这里我们直接引用MSP430的驱动库<code>#include "driverlib.h"</code>，就是因为我们在之前的头文件中进行了配置。</p>
<p>另外，在修改之前需要说明的是，<code>PortA</code>相当于<code>Port1 + Port2</code>，这里为了和原理图上的一直，均改成以数字结尾。</p>
<p><img alt="MSP430F5510芯片引脚图" src="http://mint-blog.qiniudn.com/msp430lib-MSP430F5510芯片引脚图.png" /></p>
<p>这里我们将其进行简化改写，来使其翻转一个GPIO即可。修改之后如下：</p>
<pre><code class="c">#include &quot;driverlib.h&quot;

void main(void)
{
        //停止看门狗中断
        WDT_A_hold(WDT_A_BASE);

        //设置P1.0为输出功能
        GPIO_setAsOutputPin(
                GPIO_PORT_P1,
                GPIO_PIN0
                );

        while(1){
        //设置P1.0输出为高
        GPIO_setOutputHighOnPin(
                GPIO_PORT_P1,
                GPIO_PIN0
                );
        __delay_cycles(4000);       //延时4000个circle
        //设置P1.0输出为低
        GPIO_setOutputLowOnPin(
                GPIO_PORT_P1,
                GPIO_PIN0
                );
        __delay_cycles(4000);       //延时4000个circle
        }
}
</code></pre>

<p>这里稍微解释下<code>__delay_cycles(4000);</code>，它让MSP430空转了4000个时钟周期，那么在默认情况下（即我们没有对430进行时钟设置的时候），大概多少时间呢？</p>
<p>而我们看到在官方例程中，<code>ACLK = 32.768kHz, MCLK = SMCLK = default DCO 1.045MHz</code>，430使用主时钟MCLK为系统时钟信号。貌似430的时钟也是蛮复杂的，需要配置若干寄存器，这里先不说明如何配置时钟的寄存器，具体可以参考<a href="http://wenku.baidu.com/view/32a4cf6e011ca300a6c39098.html">MSP430时钟系统讲解</a>。这里通过示波器看到翻转周期为7.74ms，那么每次翻转为7.74ms/2=3.87ms。1000/3.87*4000 = 1.033592MHz，如果将GPIO翻转的时间考虑进来，与官方的1.045MHz还是满接近的。（To Be Continued&hellip; in 时钟）</p>
<p>可以看到，其代码过程与我们的思路是完全一致的，可读性也比较高。当我们需要某个模块时，我们只要去找文档查我们需要的API即可，如<code>MSP430F5xx_6xx_DriverLib_Users_Guide-1_90_00_65.pdf</code>。</p>
<p>4、编译运行</p>
<p>可以在项目右键上点击<code>Build Project</code></p>
<p><img alt="编译结果" src="http://mint-blog.qiniudn.com/msp430lib-编译结果.png" /></p>
<p>这里我们可以看到右边有一大堆黄色的东西，那是因为CCS默认开启了它的<code>ULP Advisor</code>，简而言之，它就是一套TI建议的编程规范，按照它建议编写出来的代码具有更强的健壮性。不过话说连自己的库也是没有遵循自己的代码规范，看来这个规范的推行是多么的困难；或者说改变一个人自认为舒服的习惯是多么的困难，即使明知改变对自己是有利的。</p>
<p><img alt="ULPAdvisor" src="http://mint-blog.qiniudn.com/msp430lib-ULPAdvisor.png" /></p>
<p>最后，我们只需要点击那个<code>小虫子</code>，就可以下载并调试代码了。</p>
<h2 id="_4"><a name="user-content-_4" href="#_4" class="headeranchor-link" aria-hidden="true"><span class="headeranchor"></span></a>例程</h2>
<h3 id="_5"><a name="user-content-_5" href="#_5" class="headeranchor-link" aria-hidden="true"><span class="headeranchor"></span></a>基础篇</h3>
<h4 id="_6"><a name="user-content-_6" href="#_6" class="headeranchor-link" aria-hidden="true"><span class="headeranchor"></span></a>时钟</h4>
<p>时钟之于嵌入式硬件就像心脏之于人体。心脏的跳动为血液在全身的流动提供了源源不断的动力；晶振的振动为内设和外设的工作提供了时钟的驱动，如果木有时钟的跳动，那么这个硬件就是死的。</p>
<p>由于时钟如此的重要，所以不管430还是STM32都为其准备了我们叫做“时钟树”的东东来连接时钟源和使用时钟的设备。</p>
<p>先来看看它的时钟源和时钟信号：</p>
<p>在MSP430单片机单片机中一共有三个或四个<strong>时钟源</strong>：</p>
<ol>
<li>LFXT1CLK，为低速/高速晶振源，通常接<em>32.768kHz</em>，也可以接（400kHz～16Mhz）； </li>
<li>XT2CLK，可选高频振荡器，外接标准高速晶振，通常是接<em>8Mhz</em>，也可以接（400kHz～   16Mhz）；  </li>
<li>DCOCLK，数控振荡器，为内部晶振，由RC震荡回路构成； </li>
<li>VLOCLK，内部低频振荡器，12kHz标准振荡器。 </li>
</ol>
<p><img alt="MSP430时钟源" src="http://mint-blog.qiniudn.com/msp430lib-MSP430时钟源.png" /></p>
<p>在MSP430单片机内部一共有三个<strong>时钟信号</strong>：</p>
<ol>
<li>ACLK，Auxiliary Clock，辅助时钟，通常由LFXT1CLK或VLOCLK作为时钟源，可以通过软件控制更改时钟的分频系数；  </li>
<li>MCLK，Master Clock，系统主时钟单元，为系统内核提供时钟，它可以通过软件从四个时钟源选择； </li>
<li>SMCLK，Sub-Main Clock，系统子时钟，也是可以由软件选择时钟源。</li>
</ol>
<h4 id="_7"><a name="user-content-_7" href="#_7" class="headeranchor-link" aria-hidden="true"><span class="headeranchor"></span></a>看门狗</h4>
<p>最早听到<strong>看门狗</strong>这个概念是在大学学51的时候，然后，</p>
<p>问大神：啥是看门狗？<br />
大神言之：你在门口放一只狗，然后你要每隔一段时间就去“喂狗”，不然它就“叫”。</p>
<p>再问大神：那有啥子用处？<br />
大神言之：一般用来放置程序跑飞用的，因为如果跑飞了，那么就喂不了狗了，那么狗就叫了，比如它可以让你快速重启你的系统。</p>
<p>其实，以上只是看门狗的一个用处，其实它就是一个稍微有点特殊的定时器，多了一个“喂”就不“叫”的功能。这里用430的官方例子，主要来指定不同的时钟源来驱动看门狗。第一个是内部SMCLK；第二个是内部ACLK；最后一个就是上面大神讲的用看门狗来修复程序跑飞的示例了。</p>
<p>先瞅瞅第一个例程：<br />
<pre><code class="c">//******************************************************************************
//!
//! WDT - Toggle P1.0, Interval Overflow ISR, DCO SMCLK
//!
//!  Toggle P1.0 using software timed by the WDT ISR. Toggle rate is approx.
//!  30ms = {(default DCO 1.045MHz) / 32768} based on default DCO = SMCLK clock
//!  source used in this example for the WDT.
//!  ACLK = n/a, MCLK = SMCLK = default DCO ~1.045MHz
//!
//!  Tested On: MSP430F5529,MSP430FR5739
//!             -----------------
//!         /|\|                 |
//!          | |                 |
//!          --|RST              |
//!            |             P1.0|--&gt;LED
//!            |                 |
//!
//! This example uses the following peripherals and I/O signals.  You must
//! review these and change as needed for your own board:
//! - WDT peripheral
//! - GPIO Port peripheral
//!
//! This example uses the following interrupt handlers.  To use this example
//! in your own application you must add these interrupt handlers to your
//! vector table.
//! - WDT_A_VECTOR
//!
//******************************************************************************
#include &quot;driverlib.h&quot;
void main(void)
{
        //采用内部SMCLK时钟来初始化WDT模块，其喂狗时间间隔:
        //WDT_A_CLOCKDIVIDER_2G     :
        //WDT_A_CLOCKDIVIDER_128M   :
        //WDT_A_CLOCKDIVIDER_8192K  :   8s
        //WDT_A_CLOCKDIVIDER_512K   :   500ms
        //WDT_A_CLOCKDIVIDER_32K    :   32ms
        //WDT_A_CLOCKDIVIDER_8192   :   8ms
        //WDT_A_CLOCKDIVIDER_512    :   500us
        //WDT_A_CLOCKDIVIDER_64     :   62.5us
        WDT_A_intervalTimerInit(WDT_A_BASE,
                                WDT_A_CLOCKSOURCE_SMCLK,
                                WDT_A_CLOCKDIVIDER_32K);
        //启动看门狗A
        WDT_A_start(WDT_A_BASE);

        //使能看门狗中断，当然在使能之前最好清除一下其中断标志位
        SFR_clearInterrupt(SFR_WATCHDOG_INTERVAL_TIMER_INTERRUPT);
        SFR_enableInterrupt(SFR_WATCHDOG_INTERVAL_TIMER_INTERRUPT);

        //设置P1.0为输出模式
        GPIO_setAsOutputPin(
                GPIO_PORT_P1,
                GPIO_PIN0
                );

        //进入 LPM0, 并使能全局中断
        __bis_SR_register(LPM0_bits + GIE);
        //好让你设置断点用
        __no_operation();
}

//看门狗中断服务函数
#pragma vector=WDT_VECTOR
__interrupt
void WDT_A_ISR(void)
{
        //翻转 P1.0
        GPIO_toggleOutputOnPin(
                GPIO_PORT_P1,
                GPIO_PIN0);
}

</code></pre></p>
<p>在确定时钟源的情况下，可以改变的就是分频系数了，看例程最前面的详解可知<code>30ms = {(default DCO 1.045MHz) / 32768}</code>，大致算下就可以获得中断触发的时间了，我这里用示波器测试了下，给出了较短时间的间隔供参考。</p>
<p>第二个例程就是改变了时钟源的选择，此时的分频系数需要重新进行计算，具体改变的代码如下：<br />
<pre><code class="c">        //采用内部ACLK时钟来初始化WDT模块，其喂狗时间间隔:
        //WDT_A_CLOCKDIVIDER_2G     :
        //WDT_A_CLOCKDIVIDER_128M   :
        //WDT_A_CLOCKDIVIDER_8192K  :   250s
        //WDT_A_CLOCKDIVIDER_512K   :   16s
        //WDT_A_CLOCKDIVIDER_32K    :   1s
        //WDT_A_CLOCKDIVIDER_8192   :   250ms
        //WDT_A_CLOCKDIVIDER_512    :   16ms
        //WDT_A_CLOCKDIVIDER_64     :   2ms
        WDT_A_intervalTimerInit(WDT_A_BASE,
                                WDT_A_CLOCKSOURCE_ACLK,
                                WDT_A_CLOCKDIVIDER_64);
</code></pre></p>
<p>第三个例程将中断服务函数取消掉，此时便为<code>看门狗模式</code>，如果在预定时间内未喂狗，那么此时将会导致重启：<br />
<pre><code class="c">#include &quot;driverlib.h&quot;

void main(void)
{
        //记录翻转的次数
        static uint8_t toggle_times;

        //看门狗模式：超时时间为1s
        WDT_A_watchdogTimerInit(WDT_A_BASE,
                                WDT_A_CLOCKSOURCE_ACLK,
                                WDT_A_CLOCKDIVIDER_32K);
        //启动看门狗
        WDT_A_start(WDT_A_BASE);

        //设置P1.0为输出模式
        GPIO_setAsOutputPin(
                GPIO_PORT_P1,
                GPIO_PIN0
                );

        toggle_times++;
        //翻转 P1.0
        GPIO_toggleOutputOnPin(
                GPIO_PORT_P1,
                GPIO_PIN0
                );

        //进入 LPM3 低功耗模式
        __bis_SR_register(LPM3_bits + GIE);
        //好让你设置断点用
        __no_operation();
}
</code></pre></p>
<blockquote>
<p>此处，经过实验发现，其周期为3s左右，不知为啥扩大了3被左右的周期。</p>
</blockquote>
<p>此时的现象如下：由于是重启了芯片，所以<code>reset_times</code>的次数总是为<code>0</code>，但是GPIO是会翻转的。此时，我们需要在规定时间内进行喂狗来避免重启，我们需要在翻转之后喂狗。修改后的代码如下所示：</p>
<pre><code class="c">        while(1)
        {
            toggle_times++;
            //翻转 P1.0
            GPIO_toggleOutputOnPin(
                    GPIO_PORT_P1,
                    GPIO_PIN0
                    );
            //重新进行初始化，即喂狗
            WDT_A_watchdogTimerInit(WDT_A_BASE,
                                    WDT_A_CLOCKSOURCE_ACLK,
                                    WDT_A_CLOCKDIVIDER_32K);

            WDT_A_start(WDT_A_BASE);
            //延时，但不能超过喂狗时间，否则将会重启
            __delay_cycles(50000);
        }
</code></pre>

<h4 id="gpio"><a name="user-content-gpio" href="#gpio" class="headeranchor-link" aria-hidden="true"><span class="headeranchor"></span></a>GPIO</h4>
<p>简单的GPIO翻转已经在<code>**新建工程**</code>中演示过了，此处将介绍两个例程，第一个是将其配置为中断输入，来扑捉其边沿触发；第二个例程是将一个GPIO作为输入，然后轮训之，获得其其状态并作出相应的改变。</p>
<p>GPIO作为输入时非常常用的功能，但是从驱动库的宏定义可以看出只支持上升沿触发和下降沿触发，不支持边沿触发。<br />
<pre><code class="c">//*****************************************************************************
//
// The following are values that can be passed to the edgeSelect parameter for
// functions: GPIO_interruptEdgeSelect().
//
//*****************************************************************************
#define GPIO_HIGH_TO_LOW_TRANSITION                                      (0x01)
#define GPIO_LOW_TO_HIGH_TRANSITION                                      (0x00)

</code></pre></p>
<p>下面这个例程用 P1.0 捕获下降沿，然后捕获一次，翻转一次 P1.0 。<br />
<pre><code class="c">//******************************************************************************
//   Software Port Interrupt Service on P1.1 from LPM4 with
//                     Internal Pull-up Resistance Enabled
//
//   A hi &quot;TO&quot; low transition on P1.1 will trigger P1_ISR which,
//   toggles P1.0. P1.1 is internally enabled to pull-up. Normal mode is
//   LPM4 ~ 0.1uA. LPM4 current can be measured with the LED removed, all
//   unused Px.x configured as output or inputs pulled high or low.
//   ACLK = n/a, MCLK = SMCLK = default DCO
//
//  Tested On: MSP430F5529, MSP430FR5739, MSP430FR5510
//             -----------------
//         /|\|              XIN|-
//          | |                 |
//          --|RST          XOUT|-
//      /|\   |                 |
//       --o--|P1.1         P1.0|--&gt;LED
//      \|/
//
//   This example uses the following peripherals and I/O signals.  You must
//   review these and change as needed for your own board:
//   - GPIO Port peripheral
//
//   This example uses the following interrupt handlers.  To use this example
//   in your own application you must add these interrupt handlers to your
//   vector table.
//   - PORT1_VECTOR
//******************************************************************************
#include &quot;driverlib.h&quot;

void main(void)
{
        //关闭看门狗
        WDT_A_hold(WDT_A_BASE);

        //设置 P1.0 为输出
        GPIO_setAsOutputPin(
                GPIO_PORT_P1,
                GPIO_PIN0
                );

        //设定 P1.1 内部接上拉电阻
        //若需下降沿触发，则需接下拉电阻。
        GPIO_setAsInputPinWithPullUpresistor(
                GPIO_PORT_P1,
                GPIO_PIN1
                );

        //使能 P1.1 中断功能
        GPIO_enableInterrupt(
                GPIO_PORT_P1,
                GPIO_PIN1
                );

        //设置 P1.1 为下降沿触发
       //若需下降沿触发，则为：GPIO_HIGH_TO_LOW_TRANSITION
       //若需上升沿触发，则为：GPIO_LOW_TO_HIGH_TRANSITION
        GPIO_interruptEdgeSelect(
                GPIO_PORT_P1,
                GPIO_PIN1,
                GPIO_HIGH_TO_LOW_TRANSITION
                );


        //清除 P1.1 中断标志位 IFG
        GPIO_clearInterruptFlag(
                GPIO_PORT_P1,
                GPIO_PIN1
                );

        //进入 LPM4 低功耗模式并使能全局中断
        __bis_SR_register(LPM4_bits + GIE);

        //好让你设置断点用
        __no_operation();
}

//******************************************************************************
//
//This is the PORT1_VECTOR interrupt vector service routine
//
//******************************************************************************
#pragma vector=PORT1_VECTOR
__interrupt
void Port_1(void)
{
        //翻转 P1.0
        GPIO_toggleOutputOnPin(
                GPIO_PORT_P1,
                GPIO_PIN0
                );

        //手动清除 P1.1 中断标志位 IFG
        //注意：如果不清除，则会在本次退出之后，再次马上进入此中断。
        GPIO_clearInterruptFlag(
                GPIO_PORT_P1,
                GPIO_PIN1
                );

}
</code></pre></p>
<p>需要注意的是，GPIO 的中断标志位需要手动清除，与STM32一致，不然会在你退出中断之后又再次进入。不过比STM32要弱一点的是，所有 P1 口都共享一个中断向量，这个没有任何问题，但是没有寄存器区分 P1.x 进入了中断？！也就是说，你不能同时设置P1.0和P1.1同时作为中断源，因为没有办法区分哪个产生了中断。</p>
<p>第二个例程的作用类似，只是将中断捕获改为<code>while(1)</code>轮询输入口的状态并执行相应动作。<br />
<pre><code class="c">#include &quot;driverlib.h&quot;

void main(void)
{
        //关看门狗功能
        WDT_A_hold(WDT_A_BASE);

        //设置 P1.0 为输出模式
        GPIO_setAsOutputPin(
                GPIO_PORT_P1,
                GPIO_PIN0
                );

        //设置 P1.1 为输入模式
        GPIO_setAsInputPin(
                GPIO_PORT_P1,
                GPIO_PIN1
                );

        while (1) {
                //查询 P1.1 口状态
                if (GPIO_INPUT_PIN_HIGH == GPIO_getInputPinValue(
                            GPIO_PORT_P1,
                            GPIO_PIN1
                            )) {
                        //如果 P1.1 为高, 设置 P1.0 为高
                        GPIO_setOutputHighOnPin(
                                GPIO_PORT_P1,
                                GPIO_PIN0
                                );
                } else {
                        //否则，设置 P1.0 为低
                        GPIO_setOutputLowOnPin(
                                GPIO_PORT_P1,
                                GPIO_PIN0
                                );
                }
        }
}
</code></pre></p>
<h4 id="_8"><a name="user-content-_8" href="#_8" class="headeranchor-link" aria-hidden="true"><span class="headeranchor"></span></a>定时器</h4>
<p>定时器主要有3种工作模式：up模式、continuous模式和up/down模式。</p>
<p>例程1：P2.0 输出 PWM波。<br />
<pre><code class="c">//*******************************************************************************
//!  Timer_A3, PWM TA1.1, Up Mode, DCO SMCLK
//!
//!  Description: This program generates PWM outputs on P2.2 using
//!  Timer1_A configured for up mode. The value , TIMER_PERIOD, defines the PWM
//!  period and the value DUTY_CYCLE the PWM duty cycle. Using ~1.045MHz
//!  SMCLK as TACLK, the timer period is ~500us with a 75% duty cycle on P2.2
//!  ACLK = n/a, SMCLK = MCLK = TACLK = default DCO ~1.045MHz.
//!
//!  Tested On: MSP430F5529, MSP430F5510
//!            -------------------
//!        /|\|                   |
//!         | |                   |
//!         --|RST                |
//!           |                   |
//!           |         P2.0/TA1.1|--&gt; CCR1 - 75% PWM
//!           |                   |
//!
//! This example uses the following peripherals and I/O signals.  You must
//! review these and change as needed for your own board:
//! - Timer peripheral
//! - GPIO Port peripheral
//!
//! This example uses the following interrupt handlers.  To use this example
//! in your own application you must add these interrupt handlers to your
//! vector table.
//! - NONE
//******************************************************************************

#include &quot;driverlib.h&quot;

#define TIMER_PERIOD    511         //周期
#define DUTY_CYCLE      350         //高电平周期

void main(void)
{
        //关闭看门狗功能
        WDT_A_hold(WDT_A_BASE);

        //设置 P2.0 为 PWM 输出
        GPIO_setAsPeripheralModuleFunctionOutputPin(
                GPIO_PORT_P2,
                GPIO_PIN0
                );

        //产生 PWM 波 - 定时器采用 Up-Down 模式
        TIMER_A_generatePWM(TIMER_A1_BASE,
                            TIMER_A_CLOCKSOURCE_SMCLK,
                            TIMER_A_CLOCKSOURCE_DIVIDER_1,
                            TIMER_PERIOD,
                            TIMER_A_CAPTURECOMPARE_REGISTER_1,
                            TIMER_A_OUTPUTMODE_RESET_SET,
                            DUTY_CYCLE
                            );

        //进入 LPM0 低功耗模式
        __bis_SR_register(LPM0_bits);

        //为了便于调试，设置断点
        __no_operation();
}
</code></pre></p>
<p>问题：如何将任意的GPIO和定时器绑定之后，均可以输出PWM波呢？</p>
<p>例程2：P2.0和P2.1 多路输出 PWM波。</p>
<pre><code class="c">#include &quot;driverlib.h&quot;

#define TIMER_PERIOD    127         //周期
#define DUTY_CYCLE1     32          //CCR1 - 25% PWM
#define DUTY_CYCLE2     96          //CCR1 - 75% PWM

void main(void)
{
        //关闭看门狗
        WDT_A_hold(WDT_A_BASE);

        //设置 P2.0 和 P2.1 为功能输出
        GPIO_setAsPeripheralModuleFunctionOutputPin(
                GPIO_PORT_P2,
                GPIO_PIN0 + GPIO_PIN1
                );

        //启动定时器
        TIMER_A_configureUpDownMode( TIMER_A1_BASE,
                                     TIMER_A_CLOCKSOURCE_SMCLK,
                                     TIMER_A_CLOCKSOURCE_DIVIDER_1,
                                     TIMER_PERIOD,
                                     TIMER_A_TAIE_INTERRUPT_DISABLE,
                                     TIMER_A_CCIE_CCR0_INTERRUPT_DISABLE,
                                     TIMER_A_DO_CLEAR
                                     );

        TIMER_A_startCounter( TIMER_A1_BASE,
                              TIMER_A_UPDOWN_MODE
                              );

        //初始化比较寄存器产生PWM1
        TIMER_A_initCompare(TIMER_A1_BASE,
                            TIMER_A_CAPTURECOMPARE_REGISTER_1,
                            TIMER_A_CAPTURECOMPARE_INTERRUPT_ENABLE,
                            TIMER_A_OUTPUTMODE_TOGGLE_SET,
                            DUTY_CYCLE1
                            );
        //初始化比较寄存器产生PWM2
        TIMER_A_initCompare(TIMER_A1_BASE,
                            TIMER_A_CAPTURECOMPARE_REGISTER_2,
                            TIMER_A_CAPTURECOMPARE_INTERRUPT_DISABLE,
                            TIMER_A_OUTPUTMODE_TOGGLE_SET,
                            DUTY_CYCLE2
                            );

        //Enter LPM0
        __bis_SR_register(LPM0_bits);

        //For debugger
        __no_operation();
}

</code></pre>

<p>问题：同例程1</p>
<p>扩展1：若将<code>TIMER_A1_BASE</code>换成<code>TIMER_B0_BASE</code>，则<code>P5.7/TB0.1</code>和<code>P7.4/TB0.2</code>将输出PWM，看来定时器和PWM输出口是绑定的，不是你想让哪个输出都可以输出的。</p>
<pre><code class="c">//             -----------------
//         /|\|              XIN|-
//          | |                 |  32kHz
//          --|RST          XOUT|-
//            |                 |
//            |       P5.7/TB0.1|--&gt; CCR1 - 75% PWM
//            |       P7.4/TB0.2|--&gt; CCR2 - 25% PWM
//            |                 |
//            |                 |
//            |                 |
//            |                 |

#define TIMER_PERIOD    511         //周期
#define DUTY_CYCLE1     128          //CCR1 - 25% PWM
#define DUTY_CYCLE2     383          //CCR1 - 75% PWM
</code></pre>

<p>扩展2：<br />
将<code>TIMER_A_configureUpDownMode</code>换成<code>TIMER_A_configureUpMode</code>，则定时器从up/down模式切换到up模式。</p>
<p>例程3：定时器工作在连续模式[continuous mode]<br />
<pre><code class="c">//******************************************************************************
//!  TIMER_A, Toggle P1.0, CCR0 Cont. Mode ISR, DCO SMCLK
//!
//!  Toggle P1.0 using software and TA_0 ISR. Toggles every
//!  50000 SMCLK cycles. SMCLK provides clock source for TACLK.
//!  During the TA_0 ISR, P1.0 is toggled and 50000 clock cycles are added to
//!  CCR0. TA_0 ISR is triggered every 50000 cycles. CPU is normally off and
//!  used only during TA_ISR.
//!  ACLK = n/a, MCLK = SMCLK = TACLK = default DCO ~1.045MHz
//!
//!  Tested On: MSP430F5529,MSP430FR5739
//!         ---------------
//!     /|\|               |
//!      | |               |
//!      --|RST            |
//!        |               |
//!        |           P1.0|--&gt;LED
//!
//! This example uses the following peripherals and I/O signals.  You must
//! review these and change as needed for your own board:
//! - TimerA peripheral
//! - GPIO peripheral
//!
//! This example uses the following interrupt handlers.  To use this example
//! in your own application you must add these interrupt handlers to your
//! vector table.
//! - Timer A0
//!
//
//*****************************************************************************
#include &quot;driverlib.h&quot;

#define COMPARE_VALUE 50000     //50ms的中断周期

void main(void)
{
        //关闭看门狗
        WDT_A_hold(WDT_A_BASE);

        //设置 P1.0 为输出
        GPIO_setAsOutputPin(
                GPIO_PORT_P1,
                GPIO_PIN0
                );

        //启动由 SMCLK 驱动的连续模式的timer_a1
        TIMER_A_configureContinuousMode( TIMER_A1_BASE,
                                         TIMER_A_CLOCKSOURCE_SMCLK,
                                         TIMER_A_CLOCKSOURCE_DIVIDER_1,
                                         TIMER_A_TAIE_INTERRUPT_DISABLE,
                                         TIMER_A_DO_CLEAR
                                         );

        //初始化比较模式
        TIMER_A_clearCaptureCompareInterruptFlag(TIMER_A1_BASE,
                                                 TIMER_A_CAPTURECOMPARE_REGISTER_0
                                                 );
        TIMER_A_initCompare(TIMER_A1_BASE,
                            TIMER_A_CAPTURECOMPARE_REGISTER_0,
                            TIMER_A_CAPTURECOMPARE_INTERRUPT_ENABLE,
                            TIMER_A_OUTPUTMODE_OUTBITVALUE,
                            COMPARE_VALUE
                            );

        TIMER_A_startCounter( TIMER_A1_BASE,
                              TIMER_A_CONTINUOUS_MODE
                              );

        //进入 LPM0 低功耗模式,并打开全局中断
        __bis_SR_register(LPM0_bits + GIE);

        //便于设置断点调试
        __no_operation();
}

//******************************************************************************
//
//This is the TIMER1_A0 interrupt vector service routine.
//
//******************************************************************************
#pragma vector=TIMER1_A0_VECTOR     //注意：是A0，而不是A1
__interrupt
void TIMER1_A0_ISR(void)
{
        uint16_t compVal = TIMER_A_getCaptureCompareCount(TIMER_A1_BASE,
                                                          TIMER_A_CAPTURECOMPARE_REGISTER_0)
                           + COMPARE_VALUE;

        //Toggle P1.0
        GPIO_toggleOutputOnPin(
                GPIO_PORT_P1,
                GPIO_PIN0
                );

        //Add Offset to CCR0
        TIMER_A_setCompareValue(TIMER_A1_BASE,
                                TIMER_A_CAPTURECOMPARE_REGISTER_0,
                                compVal
                                );
}


</code></pre></p>
<p>问题：将基地址或者中断向量函数改成一致均不能进入ISR，不过为啥子定时器的基地址和中断向量对不上呢？</p>
<p>扩展：将其中的<code>TIMER_A1_BASE</code>和<code>TIMER1_A0_VECTOR</code>换成<code>TIMER_B0_BASE</code>和<code>TIMER1_B0_VECTOR</code>同样适用。</p>
<p>例程4：工作在<strong>up模式</strong>,up模式的基地址和中断向量名称统一，并且不用手动清除标志位，用起来还是蛮爽的，推荐。</p>
<pre><code class="c">//******************************************************************************
//!  Timer_B, Toggle P1.0, CCR0 Up Mode ISR, DCO SMCLK
//!
//!  Description: Toggle P1.0 using software and TB_0 ISR. Timer_B is
//!  configured for up mode, thus the timer overflows when TBR counts
//!  to CCR0. In this example, CCR0 is loaded with 50000.
//!  ACLK = n/a, MCLK = SMCLK = TBCLK = default DCO ~1.045MHz
//!
//!  Tested On: MSP430F5229
//!         ---------------
//!     /|\|               |
//!      | |               |
//!      --|RST            |
//!        |               |
//!        |           P1.0|--&gt;LED
//!
//! This example uses the following peripherals and I/O signals.  You must
//! review these and change as needed for your own board:
//! - Timer peripheral
//! - GPIO Port peripheral
//!
//! This example uses the following interrupt handlers.  To use this example
//! in your own application you must add these interrupt handlers to your
//! vector table.
//! - Timer B0
//!
//*****************************************************************************
#include &quot;driverlib.h&quot;

#define TIMER_PERIOD 50000

void main(void)
{
        //关闭看门狗
        WDT_A_hold(WDT_A_BASE);

        //设置 P1.0 为输出
        GPIO_setAsOutputPin(
                GPIO_PORT_P1,
                GPIO_PIN0
                );

        //启动定时器
        TIMER_B_clearTimerInterruptFlag(TIMER_B0_BASE);
        TIMER_B_configureUpMode(   TIMER_B0_BASE,
                                   TIMER_B_CLOCKSOURCE_SMCLK,
                                   TIMER_B_CLOCKSOURCE_DIVIDER_1,
                                   TIMER_PERIOD,
                                   TIMER_B_TBIE_INTERRUPT_DISABLE,
                                   TIMER_B_CAPTURECOMPARE_INTERRUPT_ENABLE,
                                   TIMER_B_DO_CLEAR
                                   );

        TIMER_B_startCounter(
                TIMER_B0_BASE,
                TIMER_B_UPDOWN_MODE
                );

        //Enter LPM0, enable interrupts
        __bis_SR_register(LPM0_bits + GIE);

        //For debugger
        __no_operation();
}

//******************************************************************************
//
//This is the Timer B0 interrupt vector service routine.
//
//******************************************************************************
#pragma vector=TIMERB0_VECTOR
__interrupt
void TIMERB0_ISR(void)
{
        //Toggle P1.0 using exclusive-OR
        GPIO_toggleOutputOnPin(
                GPIO_PORT_P1,
                GPIO_PIN0
                );
}
</code></pre>

<p>例程5：TIMER_A overflow<br />
<pre><code class="c">//******************************************************************************
//! TIMER_A, Toggle P1.0, Overflow ISR, 32kHz ACLK
//!
//! Description: Toggle P1.0 using software and the TIMER_A overflow ISR.
//! In this example an ISR triggers when TB overflows. Inside the ISR P1.0
//! is toggled. Toggle rate is exactly 0.25Hz = [32kHz/FFFFh]/2. Proper use of the
//! TAIV interrupt vector generator is demonstrated.
//! ACLK = TBCLK = 32kHz, MCLK = SMCLK = default DCO ~ 1.045MHz
//!
//! Tested On: MSP430F5529,MSP430FR5739,MSP430F5510
//!             ---------------
//!     /|\|               |
//!      | |               |
//!      --|RST            |
//!        |               |
//!        |           P1.0|--&gt;LED
//!
//! This example uses the following peripherals and I/O signals.  You must
//! review these and change as needed for your own board:
//! - Timer peripheral
//! - GPIO peripheral
//!
//! This example uses the following interrupt handlers.  To use this example
//! in your own application you must add these interrupt handlers to your
//! vector table.
//! - TIMER_A1
//!
//
//*****************************************************************************

#include &quot;driverlib.h&quot;

void main(void)
{
        //关闭看门狗
        WDT_A_hold(WDT_A_BASE);

        //设置 P1.0 为输出
        GPIO_setAsOutputPin(
                GPIO_PORT_P1,
                GPIO_PIN0
                );

        //启动由 ACLK 驱动的连续模式的timer_a1
        TIMER_A_clearTimerInterruptFlag(TIMER_A1_BASE);
        TIMER_A_configureContinuousMode(TIMER_A1_BASE,
                                        TIMER_A_CLOCKSOURCE_ACLK,
                                        TIMER_A_CLOCKSOURCE_DIVIDER_1,
                                        TIMER_A_TAIE_INTERRUPT_ENABLE,
                                        TIMER_A_DO_CLEAR
                                        );

        TIMER_A_startCounter(TIMER_A1_BASE,
                             TIMER_A_CONTINUOUS_MODE
                             );

        //进入 LPM0 低功耗模式, 并使能全局中断
        __bis_SR_register(LPM0_bits + GIE);

        //For debugger
        __no_operation();
}

//******************************************************************************
//
//This is the TIMER1_A1 interrupt vector service routine.
//
//******************************************************************************
#pragma vector=TIMER1_A1_VECTOR     //注意:是A1
__interrupt
void TIMER1_A1_ISR(void)
{
        //Any access, read or write, of the TAIV register automatically resets the
        //highest &quot;pending&quot; interrupt flag
        switch ( __even_in_range(TA1IV, 14) ) {
        case  0: break;                                 //No interrupt
        case  2: break;                                 //CCR1 not used
        case  4: break;                                 //CCR2 not used
        case  6: break;                                 //CCR3 not used
        case  8: break;                                 //CCR4 not used
        case 10: break;                                 //CCR5 not used
        case 12: break;                                 //CCR6 not used
        case 14:
                //Toggle P1.0                    // overflow
                GPIO_toggleOutputOnPin(
                        GPIO_PORT_P1,
                        GPIO_PIN0
                        );
                break;
        default: break;
        }
}


</code></pre></p>
<p>问题：同上上例。</p>
<p>扩展：将其中的<code>TIMER_A1_BASE</code>和<code>TIMER1_A1_VECTOR</code>换成<code>TIMER_B0_BASE</code>和<code>TIMERB1_VECTOR</code>同样适用。</p>
<h4 id="flash"><a name="user-content-flash" href="#flash" class="headeranchor-link" aria-hidden="true"><span class="headeranchor"></span></a>内部Flash</h4>
<p>Flash是非易失性储存设备，就是说掉电之后重新信息不丢失，所以一般用来储存一些常用的出厂设置参数。之于430内部Flash，其实就是用指针来操作相应的内存地址，和操作内存非常像，不过Flash在写入之前需要先擦除，即全部都搞成1，因为Flash写入的过程就是写0的过程。</p>
<p>例程1：以1B、2B或者4B的方式写入FlashB信息块。<br />
<pre><code class="c">
//*****************************************************************************
//!   Dummy calibration data is written to infoB.
//!   16 locations are written with char data, 8 locations with int data and
//!   4 locations with word data.  LED toggles upon completion.
//!
//!   Check infoB location @ 0x1900 on execution of program.
//!
//!   Tested On: MSP430F5529
//!             -----------------
//!         /|\|                 |
//!          | |             P1.0|-LED
//!          --|RST              |
//!            |                 |
//!
//! This example uses the following peripherals and I/O signals.  You must
//! review these and change as needed for your own board:
//! - Flash module
//! - GPIO Port peripheral
//!
//! This example uses the following interrupt handlers.  To use this example
//! in your own application you must add these interrupt handlers to your
//! vector table.
//! - None.
//********************************************************************************
#include &quot;driverlib.h&quot;

//*****************************************************************************
//
//INFOB address in flash into which calibration data would be written
//
//*****************************************************************************
#define INFOB_START   (0x1900)

//*****************************************************************************
//
//Dummy char array of 'calibration data' - to be stored in flash
//
//*****************************************************************************
uint8_t calibration_data_char[16] = { 0x00,
                                      0x01,
                                      0x02,
                                      0x03,
                                      0x04,
                                      0x05,
                                      0x06,
                                      0x07,
                                      0x08,
                                      0x09,
                                      0x10,
                                      0x11,
                                      0x12,
                                      0x13,
                                      0x14,
                                      0x15 };
//*****************************************************************************
//
//Dummy int array of 'calibration data' - to be stored in flash
//
//*****************************************************************************
uint16_t calibration_data_int[8] = { 0x1617,
                                     0x1819,
                                     0x2021,
                                     0x2223,
                                     0x2425,
                                     0x2627,
                                     0x2829,
                                     0x3031 };
//*****************************************************************************
//
//Dummy long array of 'calibration data' - to be stored in flash
//
//*****************************************************************************
uint32_t calibration_data_long[4] = { 0x35343332,
                                      0x39383736,
                                      0x43424140,
                                      0x47464544 };

void main(void)
{
        uint16_t status;

        //关闭看门狗
        WDT_A_hold(WDT_A_BASE);

        //设置 P1.0 为输出
        GPIO_setAsOutputPin(
                GPIO_PORT_P1,
                GPIO_PIN0
                );

        //擦除 INFOB 的128 Bytes 地址
        do {
                FLASH_segmentErase((uint8_t*)INFOB_START);
                status = FLASH_eraseCheck((uint8_t*)INFOB_START,
                                          128
                                          );
        } while (status == STATUS_FAIL);

        //往 INFOB 中写入数据：1B、2B或者4B的方式均可
        FLASH_write8(calibration_data_char,
                     (uint8_t*)INFOB_START,
                     16
                     );
        FLASH_write16(calibration_data_int,
                      (uint16_t*)(INFOB_START + 16),
                      8
                      );
        FLASH_write32(calibration_data_long,
                      (uint32_t*)(INFOB_START + 32),
                      4
                      );
        FLASH_memoryFill32(0xBEEFBEEF,
                           (uint32_t*)(INFOB_START + 48),
                           (128 - 48) &gt;&gt; 2
                           );

        while (1) {
                //翻转 P1.0
                GPIO_toggleOutputOnPin(
                        GPIO_PORT_P1,
                        GPIO_PIN0
                        );

                //延时
                __delay_cycles(1000000);
        }
}

</code></pre></p>
<p>扩展：同理可以扩展到A~D区均可，只是地址需要我们去datasheet查一下。<br />
<pre><code class="c">#define INFOA_START   (0x1980)
#define INFOB_START   (0x1900)
#define INFOC_START   (0x1880)
#define INFOD_START   (0x1800)
</code></pre></p>
<p>例程2：这里再看一个不同Flash区块之间的操作吧，其实过程是一样的。</p>
<pre><code class="c">//*****************************************************************************
//!  This program first erases flash seg C, then it increments all
//!  values in seg C, then it erases seg D, then copies seg C to seg D. LED
//!  toggles upon completion.  Starting addresses of segments defined
//!  in this code: Seg C-0x1880, Seg D-0x1800.
//!  ACLK = REFO = 32kHz, MCLK = SMCLK = default DCO 1048576Hz
//!
//!  Tested On: MSP430F5529
//!            -----------------
//!        /|\|                 |
//!         | |             P1.0|-LED
//!         --|RST              |
//!           |                 |
//!
//! This example uses the following peripherals and I/O signals.  You must
//! review these and change as needed for your own board:
//! - Flash module
//! - GPIO Port peripheral
//!
//! This example uses the following interrupt handlers.  To use this example
//! in your own application you must add these interrupt handlers to your
//! vector table.
//! - None.
//*****************************************************************************
#include &quot;driverlib.h&quot;
//*****************************************************************************
//
//INFO address in flash from which data is copied
//
//*****************************************************************************
#define INFOC_START   (0x1880)
//*****************************************************************************
//
//INFO address in flash to which data is copied into
//
//*****************************************************************************
#define INFOD_START   (0x1800)

//*****************************************************************************
//
//Number of bytes being copied
//
//*****************************************************************************
#define NUMBER_OF_BYTES   128

//*****************************************************************************
//
//Dummy array of 'calibration data' - to be stored in flash
//
//*****************************************************************************
uint8_t calibration_data[NUMBER_OF_BYTES];

//*****************************************************************************
//
//Delay cycles
//
//*****************************************************************************
#define DELAY_TIME 1000000

//*****************************************************************************
//
//Function prototypes
//
//*****************************************************************************
void write_SegC(void);
void copy_C2D(void);

void main(void)
{
        volatile uint16_t i;
        uint8_t value = 0;

        //关闭看门狗
        WDT_A_hold(WDT_A_BASE);

        //设置 P1.0 为输出
        GPIO_setAsOutputPin(
                GPIO_PORT_P1,
                GPIO_PIN0
                );

        //初始化要写入的数据
        for (i = 0; i &lt; NUMBER_OF_BYTES; i++)
                calibration_data[i] = value++;

        //将上面的数据写入C区
        write_SegC();

        //将C区的数据复制到D区
        copy_C2D();

        //无限循环
        while (1) {
                //翻转 P1.0
                GPIO_toggleOutputOnPin(
                        GPIO_PORT_P1,
                        GPIO_PIN0
                        );

                //延时
                __delay_cycles(DELAY_TIME);
        }
}

//------------------------------------------------------------------------------
//将内存中的数据写入到C区
//------------------------------------------------------------------------------
void write_SegC()
{
        uint16_t status;

        //擦除 INFOC
        do {
                FLASH_segmentErase((uint8_t*)INFOC_START);
                status = FLASH_eraseCheck((uint8_t*)INFOC_START,
                                          NUMBER_OF_BYTES
                                          );
        } while (status == STATUS_FAIL);

        //写入C区Flash
        FLASH_write8(calibration_data,
                     (uint8_t*)INFOC_START,
                     NUMBER_OF_BYTES
                     );
}

//------------------------------------------------------------------------------
//复制C区数据到D区
//------------------------------------------------------------------------------
void copy_C2D(void)
{
        char *FLASH_ptrC;
        char *FLASH_ptrD;

        uint16_t status;

        //擦除 INFOD
        do {
                FLASH_segmentErase((uint8_t*)INFOD_START);
                status = FLASH_eraseCheck((uint8_t*)INFOD_START,
                                          NUMBER_OF_BYTES
                                          );
        } while (status == STATUS_FAIL);

        //初始化C区指针
        FLASH_ptrC = (char*)INFOC_START;

        //初始化D区指针
        FLASH_ptrD = (char*)INFOD_START;

        //将C区数据写入到D区
        FLASH_write8((uint8_t*)FLASH_ptrC,
                     (uint8_t*)FLASH_ptrD,
                     NUMBER_OF_BYTES
                     );
}

</code></pre>

<p>我们看：<br />
<pre><code class="c">        //将C区数据写入到D区
        FLASH_write8((uint8_t*)FLASH_ptrC,
                     (uint8_t*)FLASH_ptrD,
                     NUMBER_OF_BYTES
                     );

</code></pre><br />
这个过程，与从内存里面获取是一致的。</p>
<h4 id="adc"><a name="user-content-adc" href="#adc" class="headeranchor-link" aria-hidden="true"><span class="headeranchor"></span></a>ADC</h4>
<p>[to be continued&hellip;]</p>
<h4 id="dac"><a name="user-content-dac" href="#dac" class="headeranchor-link" aria-hidden="true"><span class="headeranchor"></span></a>DAC</h4>
<p>[to be continued&hellip;]</p>
<h3 id="_9"><a name="user-content-_9" href="#_9" class="headeranchor-link" aria-hidden="true"><span class="headeranchor"></span></a>通信篇</h3>
<blockquote>
<p>一切的连接技术只不过让我们越发的明白：万事万物于我们竟是如此的陌生。<br />
——希瑟 . 多纳约（Heather Dnaohue）</p>
</blockquote>
<h4 id="uart"><a name="user-content-uart" href="#uart" class="headeranchor-link" aria-hidden="true"><span class="headeranchor"></span></a>UART</h4>
<p>在单片机的调试工具中，串口是最具有性价比的。有了串口，就像有了<code>printf</code>一样，我们可以看到变量的值。要是能将<code>putchar</code>重映射下，就真有了<code>printf</code>，那酸爽！！！</p>
<pre><code class="c">//******************************************************************************
//! This example shows how to configure the UART module as the loopback to
//! verify that received data is sent data.
//!
//!               MSP430F510
//!             -----------------
//!       RST -|     P4.4/UCA1TXD|----|
//!            |                 |    |
//!            |                 |    |
//!            |     P4.5/UCA1RXD|----|
//!            |                 |
//!
//!
//! This example uses the following peripherals and I/O signals.  You must
//! review these and change as needed for your own board:
//! - UART peripheral
//! - GPIO Port peripheral (for UART pins)
//! - UCA1TXD
//! - UCA1RXD
//!
//! This example uses the following interrupt handlers.  To use this example
//! in your own application you must add these interrupt handlers to your
//! vector table.
//! - USCI_A1_VECTOR.
//******************************************************************************
#include &quot;driverlib.h&quot;

//*****************************************************************************
//
//选择波特率，需要我们去手册找到相应波特率需要寄存器设置的值
//
//*****************************************************************************
#define BAUD_RATE                               115200
//*****************************************************************************
//
//Initialize received data
//
//*****************************************************************************
uint8_t receivedData = 0x00;
//*****************************************************************************
//
//Initialize trasnmit data
//
//*****************************************************************************
uint8_t transmitData = 0x00;

uint8_t check = 0;

void main(void)
{
        //关闭看门狗
        WDT_A_hold(WDT_A_BASE);

        //P4.4,5 = USCI_A1 TXD/RXD
        GPIO_setAsPeripheralModuleFunctionInputPin(
                GPIO_PORT_P4,
                GPIO_PIN4 + GPIO_PIN5
                );

        //Baudrate = 9600, clock freq = 1.048MHz
        //UCBRx = 109, UCBRFx = 0, UCBRSx = 2, UCOS16 = 0
        //Baudrate = 115200, clock freq = 1.048MHz
        //UCBRx = 9, UCBRFx = 0, UCBRSx = 1, UCOS16 = 0
        if ( STATUS_FAIL == USCI_A_UART_initAdvance(USCI_A1_BASE,
                                                    USCI_A_UART_CLOCKSOURCE_SMCLK,
                                                    9,
                                                    0,
                                                    1,
                                                    USCI_A_UART_NO_PARITY,
                                                    USCI_A_UART_LSB_FIRST,
                                                    USCI_A_UART_ONE_STOP_BIT,
                                                    USCI_A_UART_MODE,
                                                    USCI_A_UART_LOW_FREQUENCY_BAUDRATE_GENERATION ))
                return;

        //使能 UART
        USCI_A_UART_enable(USCI_A1_BASE);

        //开启接收中断
        USCI_A_UART_clearInterruptFlag(USCI_A1_BASE,
                                       USCI_A_UART_RECEIVE_INTERRUPT);
        USCI_A_UART_enableInterrupt(USCI_A1_BASE,
                                    USCI_A_UART_RECEIVE_INTERRUPT);

        //开全局中断，切记，勿忘！否则你会发现进不去串口接收中断函数。
        __enable_interrupt();

        while (1) {
                transmitData = transmitData + 1;            // 自增+1
                // 发送数据
                USCI_A_UART_transmitData(USCI_A1_BASE,
                                         transmitData);
                while (check != 1) ;
                check = 0;
        }
}

//******************************************************************************
//
//This is the USCI_A1 interrupt vector service routine.
//
//******************************************************************************
#pragma vector=USCI_A1_VECTOR
__interrupt
void USCI_A1_ISR(void)
{
        switch (__even_in_range(UCA1IV, 4)) {
        //Vector 2 - RXIFG
        case 2:
                receivedData = USCI_A_UART_receiveData(USCI_A1_BASE);
                // 检查收到的是否就是刚才发出去的
                if (!(receivedData == transmitData))

                        while (1) ;
                check = 1;
                break;
        default: break;
        }
}
</code></pre>

<p><strong>扩展</strong>：将<code>A1</code>换成<code>A0</code>，然后换下I/O口就可以了。</p>
<p>在实践部分，我将会将其发给蓝牙透传模块，实现无线蓝牙串口透传的功能。</p>
<p><strong>再扩展</strong>：如何实现通过重映射<code>putchar</code>来完成<code>printf</code>？</p>
<p>首先，按照<a href="http://processors.wiki.ti.com/index.php/Printf_support_for_MSP430_CCSTUDIO_compiler">Printf support for MSP430 CCSTUDIO compiler<br />
</a>设置我们的CCS环境，设置完成之后，我们就可以在<code>Console</code>看到我们打印输出的信息了。<br />
其次，如若想让其重映射到真实的串口（UART_A1）上，我们需要添加以下的代码：</p>
<p>声明部分，放在<code>main</code>函数前面：<br />
<pre><code class="c">#include &lt;stdio.h&gt;
#include &lt;string.h&gt;

//开宏：输出到真实串口UART_A1上
//关宏：输出到控制台Console上
#define UART_PRINTF         

#ifdef UART_PRINTF
int fputc(int _c, register FILE *_fp);
int fputs(const char *_ptr, register FILE *_fp);
#endif
</code></pre></p>
<p>定义部分，一般放在<code>main</code>函数后面：<br />
<pre><code class="c">#ifdef UART_PRINTF
int fputc(int _c, register FILE *_fp)
{
  while(!(UCA1IFG&amp;UCTXIFG));
  UCA1TXBUF = (unsigned char) _c;

  return((unsigned char)_c);
}

int fputs(const char *_ptr, register FILE *_fp)
{
  unsigned int i, len;

  len = strlen(_ptr);

  for(i=0 ; i&lt;len ; i++)
  {
    while(!(UCA1IFG&amp;UCTXIFG));
    UCA1TXBUF = (unsigned char) _ptr[i];
  }

  return len;
}
#endif
</code></pre></p>
<h4 id="iic"><a name="user-content-iic" href="#iic" class="headeranchor-link" aria-hidden="true"><span class="headeranchor"></span></a>IIC</h4>
<p>[to be continued&hellip;]</p>
<h4 id="spi"><a name="user-content-spi" href="#spi" class="headeranchor-link" aria-hidden="true"><span class="headeranchor"></span></a>SPI</h4>
<p>[to be continued&hellip;]</p>
<h4 id="usb"><a name="user-content-usb" href="#usb" class="headeranchor-link" aria-hidden="true"><span class="headeranchor"></span></a>USB</h4>
<p>[to be continued&hellip;]</p>
<h4 id="sdio"><a name="user-content-sdio" href="#sdio" class="headeranchor-link" aria-hidden="true"><span class="headeranchor"></span></a>SDIO</h4>
<p>[to be continued&hellip;]</p>
<h3 id="_10"><a name="user-content-_10" href="#_10" class="headeranchor-link" aria-hidden="true"><span class="headeranchor"></span></a>其他篇</h3>
<h4 id="crc"><a name="user-content-crc" href="#crc" class="headeranchor-link" aria-hidden="true"><span class="headeranchor"></span></a>CRC</h4>
<p>[to be continued&hellip;]</p>
<h4 id="aes"><a name="user-content-aes" href="#aes" class="headeranchor-link" aria-hidden="true"><span class="headeranchor"></span></a>AES</h4>
<p>[to be continued&hellip;]</p>
<h4 id="mlp"><a name="user-content-mlp" href="#mlp" class="headeranchor-link" aria-hidden="true"><span class="headeranchor"></span></a>MLP</h4>
<p>[to be continued&hellip;]</p>
<h2 id="_11"><a name="user-content-_11" href="#_11" class="headeranchor-link" aria-hidden="true"><span class="headeranchor"></span></a>实践</h2>
<h3 id="oled"><a name="user-content-oled" href="#oled" class="headeranchor-link" aria-hidden="true"><span class="headeranchor"></span></a>显示屏(OLED)</h3>
<p><strong>通信模块：IIC</strong></p>
<h3 id="lis3dhh3lis331dl"><a name="user-content-lis3dhh3lis331dl" href="#lis3dhh3lis331dl" class="headeranchor-link" aria-hidden="true"><span class="headeranchor"></span></a>重力传感器(LIS3DH/H3LIS331DL)</h3>
<p><strong>通信模块：IIC</strong></p>
<h3 id="flashmx66l51235f"><a name="user-content-flashmx66l51235f" href="#flashmx66l51235f" class="headeranchor-link" aria-hidden="true"><span class="headeranchor"></span></a>外部Flash(MX66L51235F)</h3>
<p><strong>通信模块：SPI</strong></p>
<h3 id="cc2540"><a name="user-content-cc2540" href="#cc2540" class="headeranchor-link" aria-hidden="true"><span class="headeranchor"></span></a>蓝牙透传(CC2540)</h3>
<p><strong>通信模块：UART</strong></p>
<h2 id="rtos"><a name="user-content-rtos" href="#rtos" class="headeranchor-link" aria-hidden="true"><span class="headeranchor"></span></a>RTOS</h2>
<h3 id="pt"><a name="user-content-pt" href="#pt" class="headeranchor-link" aria-hidden="true"><span class="headeranchor"></span></a>PT</h3>
<p>[to be continued&hellip;]</p>
<h3 id="cos"><a name="user-content-cos" href="#cos" class="headeranchor-link" aria-hidden="true"><span class="headeranchor"></span></a>μC/OS</h3>
<p>[to be continued&hellip;]</p></article></body></html>